#include "Tcp_Connection.hpp"
#include "Message.hpp"

#include <boost/asio/deadline_timer.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <list>

const int outTime = 3;
const int pushPeriod = 5;
const int bindID = 1;
const std::string initialACKString = "ACK::ENTITY::CSS::0::Client";

enum Connectiontype{
	Client = 0 ,
	Sensor,
	Unknown
};

class Connection_End_Client : public Tcp_Connection
{
	std::string lastID;
	Connectiontype m_Entity;
	int m_CnnectionID;
	boost::asio::deadline_timer timeOutTimer;
	boost::asio::deadline_timer pushTimer;
	DataType bindType;
	std::string latestInfo;
	bool isRecognized;
	bool isBound;
	bool isSetLocked;
	std::list<int> unACKList;

public:
	typedef boost::shared_ptr<Connection_End_Client> Connection_End_ClientPointer;

	Connection_End_Client(boost::asio::io_service& io_service , int connectionID ): Tcp_Connection( io_service ), timeOutTimer(io_service), pushTimer(io_service)
	{

		m_CnnectionID = connectionID;
		std::cout<<"Instantiating a new instance of Connection_End_Client with ID"<< m_CnnectionID <<std::endl;
		m_Entity == Unknown;
		lastID = "0";
		latestInfo = "PUSH";
	}

	void IncrementID() {
		int lastInt = StringToInt(lastID);
		lastInt ++;
		lastID = IntToString(lastInt);
	}

	int StringToInt(std::string input) {
		return atoi(input.c_str());
	}

	std::string IntToString(int input) {
		return boost::lexical_cast<std::string, int>(input);
	}

	void Recv_Handler(  const boost::system::error_code& error, // Result of operation.
			std::size_t bytes_transferred           // Number of bytes received.
	)
	{
		std::cout<<"In receive handler\n";

		if( !error )
		{
			std::cout<<"Connection_End_Client::Recv_Handler \n";
			std::cout<<"Received: "<<bytes_transferred<<"Bytes !"<<std::endl;
			Message ReceievedMessage;
			std::string MsgBuf = buf;
			std::cout<<"Connection_End_Client::RecvHandler:MsgBuf.length()= "<<MsgBuf.length()<<std::endl;

			if( ReceievedMessage.Decode( MsgBuf ) )
			{
				std::cout<<"Message is"<<MsgBuf<<" Length is: "<<MsgBuf.length()<<std::endl;
				if(ReceievedMessage.m_action == ENTITY && ReceievedMessage.data == "Client")
				{
					m_Entity == Client;
					SendAck(ReceievedMessage);
				}
				else if(ReceievedMessage.m_action == BIND)
				{
					bindType = ReceievedMessage.m_DataType;
					SendAck(ReceievedMessage);
				}
				else if((ReceievedMessage.m_MsgType == ACK) && (ReceievedMessage.m_action == PUSH) && (StringToInt(ReceievedMessage.ID)) == unACKList.front())
				{
					while(isSetLocked)
					{
						sleep(1);
					}
					isSetLocked = true;
					unACKList.remove(unACKList.front());
					isSetLocked = false;
				}
				else if((ReceievedMessage.m_MsgType == REQ) && (ReceievedMessage.m_action == PULL))
				{
					SendAck(ReceievedMessage);
				}
			}
			else
			{
				std::cout<<"Bad message!" << std::endl;
			}
			Receieve();
		}
		else
		{
			std::cerr<<"Error, closing connection with ID"<<m_CnnectionID<<std::endl;
			socket_.cancel();
			socket_.close();
		}

	}

	virtual	tcp::socket& socket()
	{
		std::cout<<"Returning Socket for connection ID: "<<m_CnnectionID<<std::endl;
		return socket_;
	}

	bool Handle_Send( const boost::system::error_code& error, size_t bytes_transferred )
	{
		std::cout<<"Connection_End_Client class"<<std::endl;
		std::cout<<"Sent "<<bytes_transferred<<" Bytes on Socket "<<std::endl;
		return true;
	}

	void startPush()
	{
		pushTimer.expires_from_now( boost::posix_time::seconds( pushPeriod ) );
		pushTimer.async_wait( boost::bind( &Connection_End_Client::HandlePush ,this, boost::asio::placeholders::error ) );
	}

	bool Push()
	{
		std::string pushString = "RES::PUSH::" + bindType + lastID + latestInfo;
		Send(pushString);
		//TODO check the availability of new data
		IncrementID();
		//Push message into map
		return true;
	};

	///
	//Receives a message and sends an Ack for that message.
	//
	bool SendAck( Message Msg )
	{
		Msg.m_MsgType = ACK ;
		std::string AckMsg = Msg.Encode();
		Send( AckMsg );
		std::cout<<"sent an acknowledgment message"<<AckMsg<<"."<<std::endl;
		return true;
	};

	void HandlePush(const boost::system::error_code& err)
	{
		if( err !=  boost::asio::error::operation_aborted )
		{
			std::cout << "Time to push" << std::endl;
			//TODO periodically check the availability of new data.
			Push();
		}
		else
		{
			std::cout<<" Push operation Interrupted  "<<std::endl;
		}
	}

	void SetTimeOutTimer()
	{
		timeOutTimer.expires_from_now( boost::posix_time::seconds( outTime ) );
		timeOutTimer.async_wait( boost::bind( &Connection_End_Client::HandleTimeout ,this, boost::asio::placeholders::error ) );
	}

	void HandleTimeout(const boost::system::error_code& err )
	{
		if( err !=  boost::asio::error::operation_aborted )
		{
			std::cout << "Time out, server is resending\n" << std::endl;
			if(!isRecognized) {
				Send(initialACKString);
			}
			else if(!isBound) {
				std::string bindACKString = "ACK::ENTITY::CSS::"+ bindID + IntToString(bindType);
				Send(bindACKString);
			}
			else {
				Resend();
			}
		}
		else
		{
			std::cout<<" Operation Interrupted  "<<std::endl;
		}
	}

	void Resend()
	{
		while(isSetLocked)
		{
			sleep(1);
		}
		isSetLocked = true;
		timeOutTimer.cancel();
		if(!unACKList.empty()) {
			std::list<int>::iterator iterator;
			for(iterator = unACKList.begin(); iterator != unACKList.end(); iterator ++) {
				std::string resendString = "REQ::PULL::Temp::" + IntToString(*iterator) + "::PULL";
				Send(resendString);
			}
		}
		isSetLocked = false;
		SetTimeOutTimer();
	}
};
